#include "EventLoop.h"
#include <cassert>
#include <sys/timerfd.h>
#include <sys/eventfd.h>
#include <sys/epoll.h>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <stdint.h>
#include <unistd.h>

using std::vector;
using std::list;
using std::placeholders::_1;

static const int64_t kNanosecondsPerSec = 1000 * 1000 * 1000;

static int64_t TimeStamp()
{
    struct timespec tp;
    clock_gettime(CLOCK_MONOTONIC, &tp);
    return ((int64_t)tp.tv_sec * kNanosecondsPerSec + tp.tv_nsec);
}

EventLoop::EventLoop()
    : mLoop(false),
      mTimerfd(timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC)),
      mEventfd(eventfd(0, EFD_CLOEXEC)),
      mEpollfd(epoll_create1(EPOLL_CLOEXEC))
{
    assert(mTimerfd >= 0);
    assert(mEventfd >= 0);
    assert(mEpollfd >= 0);
    AddFdInLoop(mEventfd, EPOLLIN, std::bind(&EventLoop::EventHandler, this, _1));
    AddFdInLoop(mTimerfd, EPOLLIN, std::bind(&EventLoop::TimerHandler, this, _1));
}

EventLoop::~EventLoop()
{
    assert(mLoop == false);

    for (auto iter = mDelayFunctors.begin(); iter != mDelayFunctors.end(); ++ iter)
        delete iter->second;

    if (mTimerfd >= 0)
        close(mTimerfd);
    if (mEventfd >= 0)
        close(mEventfd);
    if (mEpollfd >= 0)
        close(mEpollfd);
}

DelayEntry EventLoop::DelayRun(Functor f, double delay)
{
    MutexGruad lock(mMutex);
    int64_t timestamp = TimeStamp() + (int64_t)(delay * kNanosecondsPerSec);
    DelayEntry entry(timestamp, new Functor(std::move(f)));
    auto result = mDelayFunctors.insert(entry);
    if (result.first == mDelayFunctors.begin()) {
        ResetTimer();
    }
    return entry;
}

void EventLoop::AbortDelayFunctor(DelayEntry &entry)
{
    MutexGruad lock(mMutex);
    auto iter = mDelayFunctors.find(entry);
    if (iter == mDelayFunctors.end())
        return;
    int resetTimer = 0;
    if (iter == mDelayFunctors.begin())
        resetTimer = 1;
    mDelayFunctors.erase(iter);
    if (resetTimer) {
        ResetTimer();
    }
}

void EventLoop::Run(Functor cb)
{
    {
        MutexGruad lock(mMutex);
        mFunctors.push_back(std::move(cb));
    }
    uint64_t wakeup = 1;
    write(mEventfd, &wakeup, sizeof(uint64_t));
}

void EventLoop::EventHandler(int events)
{
    assert(events == EPOLLIN);
    uint64_t wakeup;
    ssize_t result = read(mEventfd, &wakeup, sizeof(uint64_t));
    assert(result == sizeof(uint64_t));
    list<Functor> f;
    {
        MutexGruad lock(mMutex);
        f = std::move(mFunctors);
    }
    for (auto iter = f.begin(); iter != f.end(); ++ iter) {
        if (*iter) {
            (*iter)();
        }
    }
}

void EventLoop::ResetTimer()
{
    if (!mDelayFunctors.size())
        return;

    int64_t timestamp = mDelayFunctors.begin()->first;
    struct itimerspec itp;
    itp.it_interval.tv_sec = 0;
    itp.it_interval.tv_nsec = 0;
    itp.it_value.tv_sec = timestamp / kNanosecondsPerSec;
    itp.it_value.tv_nsec = timestamp % kNanosecondsPerSec;
    timerfd_settime(mTimerfd, TFD_TIMER_ABSTIME, &itp, NULL);
}

void EventLoop::TimerHandler(int events)
{
    assert(events == EPOLLIN);
    while (1) {
        Functor *activeFunctor;
        {
            MutexGruad lock(mMutex);
            if (mDelayFunctors.size()) {
                int64_t timestamp = TimeStamp();
                if (timestamp >= mDelayFunctors.begin()->first) {
                    activeFunctor = mDelayFunctors.begin()->second;
                    mDelayFunctors.erase(mDelayFunctors.begin());
                } else {
                    break;
                }
            } else {
                break;
            }
        }
        if (*activeFunctor) {
            (*activeFunctor)();
        }
        delete activeFunctor;
    }
    uint64_t timeout;
    ssize_t result = read(mTimerfd, &timeout, sizeof(uint64_t));
    assert(result == sizeof(uint64_t));
    ResetTimer();
}

void EventLoop::AddFd(int fd, int events, IO_Functor iocb)
{
    Run(std::bind(&EventLoop::AddFdInLoop, this, fd, events, std::move(iocb)));
}

void EventLoop::AddFdInLoop(int fd, int events, IO_Functor iocb)
{
    assert(mIO_Functors.find(fd) == mIO_Functors.end());
    struct epoll_event event;
    event.events = events;
    event.data.fd = fd;
    int err = epoll_ctl(mEpollfd, EPOLL_CTL_ADD, fd, &event);
    assert(err == 0); (err);
    mIO_Functors.emplace(fd, std::move(iocb));
}

void EventLoop::DelFd(int fd)
{
    Run(std::bind(&EventLoop::DelFdInLoop, this, fd));
}

void EventLoop::DelFdInLoop(int fd)
{
    auto iter = mIO_Functors.find(fd);
    if (iter == mIO_Functors.end())
        return;
    epoll_ctl(mEpollfd, EPOLL_CTL_DEL, fd, NULL);
    mIO_Functors.erase(iter);
}

void EventLoop::ModFd(int fd, int events, IO_Functor iocb)
{
    Run(std::bind(&EventLoop::ModFdInLoop, this, fd, events, std::move(iocb)));
}

void EventLoop::ModFdInLoop(int fd, int events, IO_Functor iocb)
{
    assert(mIO_Functors.find(fd) != mIO_Functors.end());
    struct epoll_event event;
    event.events = events;
    event.data.fd = fd;
    int err = epoll_ctl(mEpollfd, EPOLL_CTL_MOD, fd, &event);
    assert(err == 0); (err);
    mIO_Functors[fd] = std::move(iocb);
}

void EventLoop::Loop()
{
    mLoop = true;
    vector<struct epoll_event> events(4);

    while (mLoop) {
        int result = epoll_wait(mEpollfd, events.data(), events.size(), -1);
        if (result < 0) {
            if (errno == EINTR) {
                continue;
            } else {
                assert(result >= 0);
            }
        }
        for (int i = 0; i < result; i++) {
            auto iter = mIO_Functors.find(events[i].data.fd);
            if (iter != mIO_Functors.end()) {
                if (iter->second) {
                    (iter->second)(events[i].events);
                } else {
                    printf("[%s: %d] epoll fd[%d] process function is null\n", __func__, __LINE__, events[i].data.fd);
                }
            } else {
                printf("[%s: %d] epoll fd[%d] callback no record!\n", __func__, __LINE__, events[i].data.fd);
            }
        }
        if (result == events.size()) {
            events.resize(events.size() * 2);
        }
    }
}

void EventLoop::Stop()
{
    mLoop = false;
    uint64_t wakeup = 1;
    write(mEventfd, &wakeup, sizeof(uint64_t));
}
